<template>
  <a-form
    ref="formRef"
    :model="formData"
    :rules="rules"
    :label-col="labelCol"
    :wrapper-col="wrapperCol"
    class="antd-form"
    style="box-sizing: border-box; overflow: hidden"
    @keyup.enter.prevent.stop="submit"
  >
    <a-row type="flex" class="antd-form-row" :gutter="0" :style="{ position: 'relative', margin: -1 * space + 'px' }">
      <template style="display: none">{{ foldCounter.reset() }}</template>
      <a-col
        v-for="item of options"
        v-bind="getColOption(colSpan, item.colSpan)"
        class="antd-form-col"
        :key="item.field"
        :style="
          Object.assign(
            {},
            item.style,
            { boxSizing: 'border-box', padding: space + 'px' },
            item.display === false || foldCounter.check() ? { display: 'none' } : {}
          )
        "
      >
        <a-form-item
          :label="item.label"
          :name="item.field"
          :rules="getRulesOption(item)"
          :label-col="getColOption(item.label === undefined ? 0 : item.labelCol)"
          :wrapper-col="getColOption(item.label === undefined ? 24 : item.wrapperCol)"
          class="antd-form-item"
        >
          <slot :name="item.field" :formData="formData">
            <component
              :is="getComponent(item.component)"
              :style="{ width: '100%' }"
              :placeholder="getPlaceholder(item)"
              v-bind="getAttrs(item)"
              v-model="formData[item.field]"
              v-model:value="formData[item.field]"
            >
              {{ typeof item.slots === 'function' ? item.slots() : item.slots }}
            </component>
          </slot>
        </a-form-item>
      </a-col>
      <template v-if="submitButtons.show !== false">
        <a-col
          v-if="submitButtons.fixRight !== false"
          v-bind="getColOption(colSpan)"
          class="antd-form-col"
          :style="{ boxSizing: 'border-box', padding: space + 'px' }"
        >
          <a-form-item class="antd-form-item"><!--占位用--></a-form-item>
        </a-col>
        <a-col
          v-bind="getColOption(colSpan)"
          class="antd-form-col"
          :style="
            Object.assign(
              { boxSizing: 'border-box', padding: space + 'px' },
              submitButtons.fixRight !== false ? { position: 'absolute', width: '100%', bottom: 0, right: 0 } : {}
            )
          "
        >
          <a-form-item class="antd-form-item" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
            <a-space
              :style="{
                width: '100%',
                display: 'flex',
                flexWrap: 'nowrap',
                alignItems: 'center',
                justifyContent: submitButtons.justifyContent,
              }"
            >
              <a-button type="primary" @click="submit">搜索</a-button>
              <a-button type="default" @click="reset">重置</a-button>
              <span
                v-if="options.filter((item) => item.display !== false).length > defaultShowCount"
                class="antd-form-folder"
                style="cursor: pointer"
                @click="fold = !fold"
              >
                {{ fold === true ? '展开∨' : '收起∧' }}
              </span>
            </a-space>
          </a-form-item>
        </a-col>
      </template>
    </a-row>
  </a-form>
</template>

<script>
import { get, isEqual } from 'lodash-es';
import * as formUtil from './form.js';

export default {
  name: 'AntdForm',
  emits: ['update:modelValue', 'change', 'submit'],
  expose: ['pickValues', 'validate', 'submit', 'reset'],
  props: {
    modelValue: { type: Object, default: () => ({}) },
    options: { type: Array, default: () => [] },
    rules: { type: Array, default: () => [] },
    gutter: { type: Number, default: 16 },
    defaultShowCount: { type: Number, default: 3 },
    colSpan: { type: Object, default: () => formUtil.defaultColSpan },
    labelCol: { type: Object, default: () => formUtil.defaultLabelColSpan },
    wrapperCol: { type: Object, default: () => formUtil.defaultWrapperColSpan },
    formButtons: { type: Object, default: () => formUtil.formButtonOption },
    immediateUpdate: { type: Boolean, default: true },
  },
  data() {
    return {
      formData: {},
      fold: true,
    };
  },
  computed: {
    space() {
      const gutter = Number(this.gutter);
      return (Number.isFinite(gutter) ? gutter : 16) / 2;
    },
    submitButtons() {
      const isOverCount = this.options.length > this.defaultShowCount;
      const formButtons = Object.assign({}, this.formButtons);
      return Object.assign({}, formUtil.formButtonOption, formButtons, {
        justifyContent: formButtons.justifyContent || (isOverCount ? 'flex-end' : 'flex-start'),
        show: this.formButtons !== null,
        fixRight: typeof formButtons.fixRight === 'boolean' ? formButtons.fixRight : isOverCount,
      });
    },
    foldCounter() {
      let count = this.defaultShowCount;
      return {
        reset: () => (count = this.defaultShowCount),
        check: () => this.submitButtons.show !== false && this.fold === true && count-- <= 0,
      };
    },
  },
  created() {
    this.fieldsOption = formUtil.getDataFromOptions({
      options: this.options,
      valueField: 'valueType',
      resultType: 'object',
    });
    this.fields = formUtil.getDataFromOptions({
      options: this.options,
      valueField: 'field',
      resultType: 'array',
    });
    this.values = formUtil.getDataFromOptions({
      options: this.options,
      valueField: 'value',
      resultType: 'object',
    });
    const modelValue = formUtil.formatToMultiFieldsData(this.modelValue, this.fieldsOption);
    this.formData = formUtil.mergeFormData(modelValue, this.values, this.fields);
    this.$watch(
      () => this.modelValue,
      (modelValue) => {
        modelValue = formUtil.formatToMultiFieldsData(modelValue, this.fieldsOption);
        modelValue = formUtil.pickValues(modelValue, this.fields);
        if (!isEqual(this.formData, modelValue)) {
          this.formData = Object.assign({}, this.formData, modelValue);
        }
      },
      { immediate: false, deep: false }
    );

    this.$watch(
      () => this.formData,
      () => this.output(),
      { immediate: this.immediateUpdate !== false, deep: true }
    );
  },
  methods: {
    getComponent: formUtil.getComponent,
    getColOption: formUtil.getColOption,
    getRulesOption: formUtil.getRulesOption,
    getPlaceholder: formUtil.getPlaceholder,
    getAttrs(item) {
      const attrs = Object.assign({}, item.attrs, item.props);
      const agentEvents = Object.keys(attrs).reduce((res, key) => {
        if (!/^on[A-Z]/.test(key) || typeof attrs[key] !== 'function') {
          return res;
        }
        const handler = async function () {
          this.outputReady instanceof Promise && (await this.outputReady);
          attrs[key](...arguments);
        }.bind(this);
        return Object.assign(res, { [key]: handler });
      }, {});
      return Object.assign({}, attrs, agentEvents);
    },
    pickValues(source) {
      return formUtil.pickValues(source, this.fields);
    },
    async output() {
      this.outputReady = new Promise((resolve) => {
        let values = formUtil.formatToSingleFieldsData(this.formData, this.fieldsOption);
        values = Object.assign({}, this.modelValue, values);
        if (isEqual(values, this.modelValue)) {
          return resolve(values);
        }
        this.$emit('update:modelValue', values);
        this.$emit('change', values);
        resolve(values);
      });
      return await this.outputReady;
    },
    async validate() {
      return !!(await this.$refs.formRef.validate().catch((error) => {
        this.$refs.formRef.scrollToField(get(error, 'errorFields[0].name[0]'));
      }));
    },
    async submit() {
      const ok = await this.validate();
      if (ok) {
        const values = await this.output();
        this.$emit('submit', values);
        return values;
      }
    },
    async reset() {
      this.formData = Object.assign({}, this.values);
      return await this.submit();
    },
  },
};
</script>

<style lang="less">
.antd-form {
  .antd-form-row {
    .antd-form-col {
      .antd-form-item {
        margin: 0;
      }
    }
  }
}
</style>
